package spacegame;

/**
 * The abstract base class for Map
 * @author Pucella
 *
 */
public abstract class Map {
  public static Map empty(int x, int y) {
    return new MapEmpty(x, y);
  }
  
  public static Map artifact(int x, int y, Artifact a) {
    if (a.position().within(Coord.create(0, 0), Coord.create(x - 1, y - 1)))
      return new MapArtifact(x, y, a);
    throw new IllegalArgumentException("artifact()");
  }
  
  public static Map merge(Map m1, Map m2) {
    if (m1.size().equals(m2.size()))
      return new MapMerge(m1, m2);
    throw new IllegalArgumentException("merge()");
  }
  
  public abstract Coord size();
  
  public abstract boolean isEmpty();
  
  public abstract Artifact firstArtifact();
  
  public abstract Map restArtifacts();
  
  public abstract String toString();
  
  public abstract int hashCode();
  
  public boolean equals(Object obj) {
    if (obj instanceof Map) {
      Map m = (Map) obj;
      if (!(this.size().equals(m.size())))
        return false;
      if (this.isEmpty() && m.isEmpty())
        return true;
      if (this.isEmpty() || m.isEmpty())
        return false;
      // at this point, sizes agree, and the maps are non-empty
      return (this.firstArtifact().equals(m.firstArtifact()) && 
          this.restArtifacts().equals(m.restArtifacts()));
    }
    return false;
  }
  
  public abstract FuncIterator<Artifact> getFuncIterator();
  
  public Option<Artifact> findArtifactByName(String string) {
    // TODO Auto-generated method stub
    return null;
  }
  
  public Map replaceArtifact(String valOf, Artifact result) {
    // TODO Auto-generated method stub
    return null;
  }
  
  public Map removeArtifact(String valOf) {
    // TODO Auto-generated method stub
    return null;
  }
  
  public Map moveArtifact(String valOf, int dx, int dy) {
    // TODO Auto-generated method stub
    return null;
  }

  public Option<Artifact> findArtifactByPosition(Coord c) {
    // TODO Auto-generated method stub
    return null;
  }

  public Map removeArtifact(Artifact valOf) {
    // TODO Auto-generated method stub
    return null;
  }

  public Map replaceArtifact(Artifact valOf, Artifact result) {
    // TODO Auto-generated method stub
    return null;
  }

  public Map moveArtifact(Movable valOf, int dx, int dy) {
    // TODO Auto-generated method stub
    return null;
  }
}


/**
 * The concrete classes for Map
 * @author Pucella
 *
 */
class MapEmpty extends Map {
  private int xsize;
  private int ysize;
  
  public MapEmpty(int x, int y) {
    xsize = x;
    ysize = y;
  }
  
  public Coord size() {
    return Coord.create(xsize, ysize);
  }
  
  public boolean isEmpty() {
    return true;
  }
  
  public Artifact firstArtifact() {
    throw new Error("empty().firstArtifact() undefined");
  }
  
  public Map restArtifacts() {
    throw new Error("empty().restArtifacts() undefined");
  }
  
  public String toString() {
    return "";
  }
  
  public int hashCode() {
    return 0;
  }
  
  public FuncIterator<Artifact> getFuncIterator() {
    return new FuncIteratorEmpty();
  }
}


/**
 * @author Pucella
 *
 */
class MapArtifact extends Map {
  private int      xsize;
  private int      ysize;
  private Artifact artifact;
  
  public MapArtifact(int x, int y, Artifact a) {
    xsize = x;
    ysize = y;
    artifact = a;
  }
  
  public Coord size() {
    return Coord.create(xsize, ysize);
  }
  
  public boolean isEmpty() {
    return false;
  }
  
  public Artifact firstArtifact() {
    return artifact;
  }
  
  public Map restArtifacts() {
    return Map.empty(this.size().xPos(), this.size().yPos());
  }
  
  public String toString() {
    return artifact.toString();
  }
  
  public int hashCode() {
    return artifact.hashCode();
  }
  
  public FuncIterator<Artifact> getFuncIterator() {
    return new FuncIteratorArtifact(artifact);
  }
}


class MapMerge extends Map {
  private Map map1;
  private Map map2;
  
  public MapMerge(Map m1, Map m2) {
    map1 = m1;
    map2 = m2;
  }
  
  public Coord size() {
    return map1.size();
  }
  
  public boolean isEmpty() {
    return map1.isEmpty() && map2.isEmpty();
  }
  
  public Artifact firstArtifact() {
    if (map1.isEmpty())
      return map2.firstArtifact();
    else
      return map1.firstArtifact();
  }
  
  public Map restArtifacts() {
    if (map1.isEmpty())
      return map2.restArtifacts();
    else
      return Map.merge(map1.restArtifacts(), map2);
  }
  
  public String toString() {
    return map1.toString() + map2.toString();
  }
  
  public int hashCode() {
    return map1.hashCode() + map2.hashCode();
  }
  
  public FuncIterator<Artifact> getFuncIterator() {
    return new FuncIteratorMerge(map1.getFuncIterator(), map2.getFuncIterator());
  }
}


//
// Classes implementing the various functional iterators
//
class FuncIteratorEmpty implements FuncIterator<Artifact> {
  public FuncIteratorEmpty() {}
  
  public boolean hasElement() {
    return false;
  }
  
  public Artifact current() {
    throw new java.util.NoSuchElementException("Iterator empty");
  }
  
  public FuncIterator<Artifact> advance() {
    throw new java.util.NoSuchElementException("Iterator empty");
  }
}


class FuncIteratorArtifact implements FuncIterator<Artifact> {
  private Artifact artifact;
  
  public FuncIteratorArtifact(Artifact a) {
    artifact = a;
  }
  
  public boolean hasElement() {
    return true;
  }
  
  public Artifact current() {
    return this.artifact;
  }
  
  public FuncIterator<Artifact> advance() {
    return new FuncIteratorEmpty();
  }
}


class FuncIteratorMerge implements FuncIterator<Artifact> {
  private FuncIterator<Artifact> iter1;
  private FuncIterator<Artifact> iter2;
  
  public FuncIteratorMerge(FuncIterator<Artifact> i1, FuncIterator<Artifact> i2) {
    iter1 = i1;
    iter2 = i2;
  }
  
  public boolean hasElement() {
    return (iter1.hasElement() || iter2.hasElement());
  }
  
  public Artifact current() {
    if (iter1.hasElement())
      return iter1.current();
    else
      return iter2.current();
  }
  
  public FuncIterator<Artifact> advance() {
    if (iter1.hasElement())
      return new FuncIteratorMerge(iter1.advance(), iter2);
    return iter2.advance();
  }
}
